#!/usr/bin/env python2.7
"""
Simple interpreter for a "language" used to play around with lambda calculus.

It has two types of statements: assignments and evaluations.

Assignments are of the form <name> = <expression>, where name is alphanumeric
and expression is an expression in lambda calculus, eg. '/xy.xy'.

Evaluations are simply assigned names separated by whitespace and grouped with
parentheses, eg. 'SUCC (SUCC ONE)'. These are printed when interpreted.

One statement per line, commented lines start with '#'.
"""
import argparse
import re

import parser
import reducer

def simplify(s):
    """Return a reduced representation of s."""
    return parser.unparse(reducer.reduceExpr(parser.parse(s)))

def reduceWithSteps(node):
    """
    Reduce expression as much as possible and return a list of nodes showing
    each step in the reduction.
    """
    nodes = [node]
    while True:
        newNode = reducer.reduceOnce(node)
        if newNode == node:
            return nodes
        node = newNode
        nodes.append(newNode)

def evaluate(s, varDict):
    """
    Return list of nodes showing reduction of a statement and store in varDict
    if s is an assignment.

    Return None if comment or blank line.
    """
    s = s.strip()
    if '=' in s:
        name, rhs = s.split('=', 1)

        nodes = evaluate(rhs.strip(), varDict)
        varDict[name.strip()] = parser.unparse(nodes[-1])
        return nodes
    elif s and not s.startswith('#'):
        for name in varDict:
            s = re.sub(r'\b%s\b' % name, '(%s)' % varDict[name], s)
        s = s.replace(' ', '')
        return reduceWithSteps(parser.parse(s))
    return None

argParser = argparse.ArgumentParser(description='Lambda-lang interpreter.')

argParser.add_argument('file', default=None, nargs='?')
argParser.add_argument(
        '-d',
        '--debug',
        help='print debug info',
        action='store_true'
        )
argParser.add_argument(
        '-v',
        '--verbose',
        help='print reduction steps',
        action='store_true'
        )

args = argParser.parse_args()

nameToVal = dict()
if args.file is not None:
    lines = open(args.file).read().splitlines()

    for line in lines:
        if '=' in line:
            line = line.replace(' ', '')
            name, val = line.split('=')
            nameToVal[name] = val
        elif line and not line.startswith('#'):
            for name in nameToVal:
                line = re.sub(r'\b%s\b' % name, '(%s)' % nameToVal[name], line)
            line = line.replace(' ', '')
            print simplify(line)
            if args.debug:
                print reducer.reduceExpr(parser.parse(line))
else:
    print 'Lambda-lang interactive interpreter'
    while True:
        try:
            statement = raw_input('>>> ')
        except (KeyboardInterrupt, EOFError):
            print '\nGoodbye.'
            break

        try:
            nodes = evaluate(statement, nameToVal)
        except RuntimeError:
            print 'Recursion depth exceeded'
            nodes = None
        except:
            print 'Syntax error'
            nodes = None

        if nodes is None:
            continue
        if args.verbose:
            for node in nodes:
                print parser.unparse(node)
        else:
            print parser.unparse(nodes[-1])
